"""Python中的 *号是一个特殊的符号，在其他编程语言中，它最广为人知的用途就是作为乘法运算的符号。
而在Python中，它的用途远不止如此。

本文总结了Python中*号的所有用途，以供参考。

1. 算术运算
号用来做算术运算几乎是所有编程语言采用的方法，
在Python中，号可作为乘法运算和指数运算。

比如："""

a = 3
b = 5
print("乘法：3×5 = {}".format(a * b))
print("指数：3的5次方 = {}".format(a ** b))

"""# 运行结果
乘法：3×5 = 15
指数：3的5次方 = 243
2. 构造与解构
除了基本的算术运算，*号在Python中还可以用在数据结构的构造和解构中。

2.1. 列表的构造
如果要在一些数据中间的某个位置插入一个现有的列表，来构造一个新列表的话，
大部分语言只能通过循环来实现。

而Python中的*号，可以让我们用一行代码就实现。"""

lst = [1, 2, 3]
new_lst = [0, lst, 4, 5]
print("不使用*号，构造后的列表：{}".format(new_lst))

new_lst = [0, *lst, 4, 5]
print("使用*号，构造后的列表：{}".format(new_lst))

"""# 运行结果
不使用*号，构造后的列表：[0, [1, 2, 3], 4, 5]
使用*号，构造后的列表：[0, 1, 2, 3, 4, 5]
使用*号，可以自动将现有列表中的元素展开。

2.2. 列表的解构
简单来说，解构就是将列表中的元素分配给几个变量。
比如下面的代码，利用*号，可以迅速将一个列表中的元素分为3个部分："""

# 列表解构
first, *lst, last = new_lst
print("列表第一个元素：{}".format(first))
print("列表中间元素：{}".format(lst))
print("列表最后一个元素：{}".format(last))

"""# 运行结果
列表第一个元素：0
列表中间元素：[1, 2, 3, 4]
列表最后一个元素：5
2.3. 字典的构造
字典的构造是用 两个*号。"""

dict = {"name": "harry", "age": 40}
new_dict = {"gender": "male", **dict}
print("构造后的字典：{}".format(new_dict))

"""# 运行结果
构造后的字典：{'gender': 'male', 'name': 'harry', 'age': 40}
这样，就把已有字典中的key/value展开到新的字典中去了。

注意，字典类型的变量如果不加双*号，是会报错的，比如：

new_dict = { "gender": "male", dict}
# 这样写会报语法错误
字典只有构造的方法，没有类似列表的解构方法。

3. 函数参数
*号用在函数参数中，可以定义更加灵活的参数形式。

3.1. 不定长参数
不定长参数让函数更加灵活，比如 print函数的参数就是不定长的，传入几个参数，它就打印几个。
再比如，我们构造一个求和的函数，希望可以对任意数量的整数求和，就可以用*号来实现。
"""


# 求和函数
def add(*numbers):
    sum = 0
    for number in numbers:
        sum += number

    return sum


# 使用此函数时，可以传入任意数量的参数：

# 可以传入任意数目的参数
sum = add(1, 2, 3)
print("1~3 求和：{}".format(sum))
sum = add(1, 2, 3, 4)
print("1~4 求和：{}".format(sum))
print(add(*[1,2,3,4]))

"""# 运行结果
1~3 求和：6
1~4 求和：10
当然，对于有 *号 的参数，也可以传入列表变量作为参数，不过列表变量前要加 *号。"""

lst = [1, 2, 3]
sum = add(*lst)  # 变量 lst 前不加 *号 会报错
print(sum)
"""3.2. 不定长的关键字参数
所谓关键字参数，就是传入参数的时候，不仅传入参数的值，还传入参数的名称。
比如下面一个模拟改变对象属性的函数：
"""


def change(obj, **attrs):
    for key, val in attrs.items():
        obj[key] = val


"""通过参数**attr，函数可以修改obj对象的任意属性。
这样的好处是不用定义多个不同的函数来修改obj的不同属性。"""

person = {"name": "harry", "age": 30, "gender": "male"}
print("修改前：{}".format(person))

# 调用方式
change(person, age=40, name="jack")
print("修改后：{}".format(person))

# 运行结果
"""修改前：{'name': 'harry', 'age': 30, 'gender': 'male'}
修改后：{'name': 'jack', 'age': 40, 'gender': 'male'}
4. 限制函数调用
最后的这个*号的用途比较罕见，如果你知道这个用途的话，我会对你很佩服。

先定义一个示例函数："""


def self_introduce(name, age):
    print("大家好，我是 {}, 今年 {} 岁。".format(name, age))


"""这个函数很简单，传入name和age两个参数，然后打印一段简单的自我介绍。

在Python中，我们可以用下面两种方式调用这个函数：
"""
# 方式一
self_introduce("harry", 40)
# 运行结果
# 大家好，我是 harry, 今年 40 岁。

# 方式二
self_introduce(name="harry", age=40)
# 运行结果
# 大家好，我是 harry, 今年 40 岁。
# 两种方式的效果是是一样的。
"""方式一也可以叫做位置参数调用法；
方式二也可以叫做关键字参数调用法。

4.1. 只能用关键字参数方式调用
如果我们想限制self_introduce只能用方式二（关键字参数）来调用，可以：
"""


# 函数的第一个参数用 * 号
def self_introduce(*, name, age):
    print("大家好，我是 {}, 今年 {} 岁。".format(name, age))


# 这样调用时，只能使用方式二了。

# 这样调用会报错
self_introduce("harry", 40)

# 可以正常执行
self_introduce(name="harry", age=40)


# 4.2. 只能用位置参数方式调用
# 如果想限制self_introduce只能用方式一（位置参数）来调用，可以：

# 函数的最后一个参数用 / 号
def self_introduce(name, age, /):
    print("大家好，我是 {}, 今年 {} 岁。".format(name, age))


# 这样调用时，只能使用方式一了。

# 可以正常执行
self_introduce("harry", 40)

# 这样调用会报错
self_introduce(name="harry", age=40)
